package com.shop.cereshop.app.job;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.PageHelper;
import com.shop.cereshop.app.dao.recommend.CereRecommendTrendsDAO;
import com.shop.cereshop.app.dao.recommend.CereRecommendTypeDAO;
import com.shop.cereshop.app.page.recommend.RecommendPageVO;
import com.shop.cereshop.app.page.recommend.RecommendType;
import com.shop.cereshop.app.page.recommend.RecommendTypeVO;
import com.shop.cereshop.app.service.recommend.CereRecommendTypeService;
import com.shop.cereshop.app.utils.RedisUtil;
import com.shop.cereshop.commons.domain.common.PageParam;
import com.shop.cereshop.commons.domain.recommend.CereRecommendTrends;
import com.shop.cereshop.commons.enums.RedisEnum;
import com.shop.cereshop.commons.enums.recommend.RecommendLikeTypeEnum;
import com.shop.cereshop.commons.utils.EmptyUtils;
import com.shop.cereshop.commons.utils.TimeUtils;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.annotation.XxlJob;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.redis.core.RedisOperations;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.SessionCallback;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.*;
import java.util.stream.Collectors;

/**
 * @author 罗霄
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class RecommendJob {

    private final RedisTemplate redisTemplate;

    private final CereRecommendTypeService cereRecommendTypeService;

    private final CereRecommendTypeDAO cereRecommendTypeDAO;

    private final CereRecommendTrendsDAO cereRecommendTrendsDAO;

    @Value("${query.params.recommend.recommend_num1}")
    private Integer recommendNumber1;

    @Value("${query.params.recommend.recommend_num2}")
    private Integer recommendNumber2;

    @Value("${time.format}")
    private String timeFormat;

    @Value("${query.params.recommend.batch_size}")
    private Integer recommendBatchSize;

    @Resource
    private RedisUtil redisUtil;

    /**
     * 存储热门话题及话题下的热门动态
     */
    @XxlJob("saveRecommendTypeAndTrends")
    @Scheduled(fixedDelay = 1000 * 60 * 5)
    public void saveRecommendTypeAndTrends() {
        System.out.println("开始计算热门话题及话题下的热门动态列表");
        XxlJobHelper.log("开始计算热门话题及话题下的热门动态列表");

        // 先执行热门话题计算
        String hashKey = RedisEnum.RECOMMEND_TYPE.getKey();
        String zsetKey = RedisEnum.RECOMMEND_TYPE_TOP.getKey();

        List<Long> recommendTypeIds;
        try {
            //设置分页参数
            PageParam pageParam = new PageParam();
            pageParam.setPageSize(recommendNumber1);
            pageParam.setPage(1);
            PageHelper.startPage(pageParam.getPage(), pageParam.getPageSize());
            //获取热门话题下的贴子数和分组集合
            List<RecommendType> recommendTypes = cereRecommendTypeService.getRecommendTypes();
            HashMap<Long, Integer> recommendCountMap = new HashMap<>();
            HashMap<Long, Integer> viewCountMap = new HashMap<>();

            //遍历热门话题
            for (RecommendType recommendType : recommendTypes) {
                //分配每个话题下的动态数和浏览数
                Long typeId = recommendType.getRecommendTypeId();
                recommendCountMap.merge(typeId, recommendType.getRecommendCount(), Integer::sum);
                viewCountMap.merge(typeId, recommendType.getViewCount(), Integer::sum);
            }

            //获取所有热门话题id集合
            recommendTypeIds = new ArrayList<>(recommendCountMap.keySet());

            if (CollUtil.isNotEmpty(recommendTypeIds)) {
                // 使用管道操作减少网络开销
                redisTemplate.executePipelined(new SessionCallback<Object>() {
                    @Override
                    public <K, V> Object execute(@NotNull RedisOperations<K, V> operations) {
                        // 先清理旧的数据
                        operations.delete((K) zsetKey);
                        operations.delete((K) hashKey);

                        for (RecommendType recommendType : recommendTypes) {
                            Integer recommendCount = recommendCountMap.getOrDefault(recommendType.getRecommendTypeId(), 0);
                            Integer viewCount = viewCountMap.getOrDefault(recommendType.getRecommendTypeId(), 0);
                            double score = Math.round((recommendCount * 0.7 + viewCount * 0.3) * 100.0) / 100.0;
                            RecommendTypeVO recommendTypeVO = BeanUtil.copyProperties(recommendType, RecommendTypeVO.class);
                            recommendTypeVO
                                    .setRecommendCount(recommendCount)
                                    .setViewCount(viewCount);

                            // 存储到有序集合（排行榜）
                            operations.opsForZSet().add((K) zsetKey, (V) String.valueOf(recommendType.getRecommendTypeId()), score);
                            operations.opsForHash().put((K) hashKey, (Object) String.valueOf(recommendType.getRecommendTypeId()), recommendTypeVO);
                        }
                        return null;
                    }
                });
            }

            System.out.println("热门话题列表计算完成，共处理 " + recommendTypeIds.size() + " 个话题");
            XxlJobHelper.log("热门话题列表计算完成，共处理 " + recommendTypeIds.size() + " 个话题");
        } catch (Exception e) {
            System.err.println("计算热门话题列表时发生错误: " + e.getMessage());
            XxlJobHelper.log("计算热门话题列表时发生错误: " + e.getMessage());
            e.printStackTrace();
            throw new RuntimeException("计算热门话题列表时发生错误", e); // 重新抛出异常以便XXL-JOB能够捕获并记录
        }

        // 再执行热门话题下的热门动态计算
        try {
            System.out.println("开始计算热门话题下的热门动态列表");
            XxlJobHelper.log("开始计算热门话题下的热门动态列表");

            //根据话题id集合查询每个话题下热门的动态列表数据
            Map<Long, List<RecommendPageVO>> map = new HashMap<>();
            PageParam pageParam1 = new PageParam();
            pageParam1.setPageSize(4);
            pageParam1.setPage(1);
            if (EmptyUtils.isNotEmpty(recommendTypeIds)) {
                //批量查询热门话题下的热门动态
                List<RecommendPageVO> recommends = cereRecommendTrendsDAO.getTrendsByTypeIds(recommendTypeIds);
                map = recommends.stream().collect(Collectors.groupingBy(RecommendPageVO::getCurrentTypeId));
            }

            //存储热门动态到redis，按话题id分别存储
            for (Map.Entry<Long, List<RecommendPageVO>> entry : map.entrySet()) {
                Long typeId = entry.getKey();
                List<RecommendPageVO> pageVOs = entry.getValue();
                String typeTrendsKey = RedisEnum.RECOMMEND_TRENDS.getKey() + ":" + typeId;
                for (RecommendPageVO recommendPageVO : pageVOs) {
                    redisTemplate.opsForHash().put(typeTrendsKey, String.valueOf(recommendPageVO.getRecommendId()), recommendPageVO);
                }

                //同时存储该话题下动态的热度排序
                String typeHotTrendsKey = RedisEnum.RECOMMEND_TYPE_HOT_TRENDS.getKey() + ":" + typeId;
                for (RecommendPageVO recommendPageVO : pageVOs) {
                    //统计点赞数+评论数+浏览数
                    double score = Math.round((recommendPageVO.getLikeCount() * 0.3 + recommendPageVO.getCommentCount() * 0.3 + recommendPageVO.getBrowseCount() * 0.4) * 100.0) / 100.0;
                    redisTemplate.opsForZSet().add(typeHotTrendsKey, String.valueOf(recommendPageVO.getRecommendId()), score);
                }
            }

            System.out.println("热门话题下的热门动态列表计算完成");
            XxlJobHelper.log("热门话题下的热门动态列表计算完成");
        } catch (Exception e) {
            System.err.println("计算热门话题下的热门动态列表时发生错误: " + e.getMessage());
            XxlJobHelper.log("计算热门话题下的热门动态列表时发生错误: " + e.getMessage());
            log.warn("计算热门话题列表时发生错误: {}", e.getMessage());
            throw new RuntimeException("计算热门话题下的热门动态列表时发生错误", e);
        }
    }


    /*
     * 存储首页热门动态列表
     * */
    @XxlJob("saveIndexHotTrends")
    @Scheduled(fixedDelay = 1000 * 60)
    public void saveIndexHotTrends() {
        System.out.println("开始计算首页热门动态列表");
        XxlJobHelper.log("开始计算首页热门动态列表");

        //存储热门动态列表到redis（用于无话题ID查询）
        PageParam page = new PageParam();
        page.setPageSize(recommendNumber2);
        page.setPage(1);
        //分页查询所有动态
        PageHelper.startPage(page.getPage(), page.getPageSize());
        List<RecommendPageVO> recommendPageVOList = cereRecommendTrendsDAO.getBatchByIds(null, null, null);
        recommendPageVOList.forEach(RecommendPageVO::init);
        for (RecommendPageVO recommendPageVO : recommendPageVOList) {
            redisTemplate.opsForHash().put(RedisEnum.RECOMMEND_TRENDS.getKey(), String.valueOf(recommendPageVO.getRecommendId()), recommendPageVO);
            double score = Math.round((recommendPageVO.getLikeCount() * 0.3 + recommendPageVO.getBrowseCount() * 0.4 + recommendPageVO.getCommentCount() * 0.3) * 100.0);
            redisTemplate.opsForZSet().add(RedisEnum.RECOMMEND_HOT_TRENDS.getKey(), String.valueOf(recommendPageVO.getRecommendId()), score);
        }
    }

    @XxlJob("syncRecommendCount")
    @Scheduled(fixedDelay = 1000 * 60 * 3)
    public void syncRecommendCount() {
        System.out.println("开始同步动态点赞数、浏览数、评论数");
        log.info("开始同步动态点赞数、浏览数、评论数");


        Map<String, Long> likeCounts = redisUtil.getAllHashData(RedisEnum.USER_LIKE_KEY.getKey());
        Map<String, Long> commentCounts = redisUtil.getAllHashData(RedisEnum.USER_COMMENT_KEY.getKey());
        Map<String, Long> collectCounts = redisUtil.getAllHashData(RedisEnum.USER_COLLECT_KEY.getKey());
        //提取likeCounts  commentCounts collectCounts 的key组成一个新的集合 并将string转位int
        Set<Long> recommendIds = new HashSet<>();
        if (CollUtil.isNotEmpty(likeCounts)) {
            recommendIds.addAll(likeCounts.keySet().stream().map(Long::parseLong).collect(Collectors.toSet()));
        }
        if (CollUtil.isNotEmpty(commentCounts)) {
            recommendIds.addAll(commentCounts.keySet().stream().map(Long::parseLong).collect(Collectors.toSet()));
        }
        if (CollUtil.isNotEmpty(collectCounts)) {
            recommendIds.addAll(collectCounts.keySet().stream().map(Long::parseLong).collect(Collectors.toSet()));
        }

        if (EmptyUtils.isEmpty(recommendIds)) {
            XxlJobHelper.log("没有需要同步的动态数据");
            log.info("没有需要同步的动态数据");
            return;
        }
        QueryWrapper<CereRecommendTrends> queryWrapper = new QueryWrapper<>();
        queryWrapper.in("recommend_id", recommendIds);
        List<CereRecommendTrends> cereRecommendTrends = cereRecommendTrendsDAO.selectList(queryWrapper);

        for (CereRecommendTrends cereRecommendTrend : cereRecommendTrends) {
            Long recommendId = cereRecommendTrend.getRecommendId();
            if (CollUtil.isNotEmpty(likeCounts)) {
                Long likeCount = likeCounts.getOrDefault(String.valueOf(recommendId), 0L);
                cereRecommendTrend.setLikeCount(cereRecommendTrend.getLikeCount() + Integer.parseInt(likeCount.toString()));
            }
            if (CollUtil.isNotEmpty(commentCounts)) {
                Long commentCount = commentCounts.getOrDefault(String.valueOf(recommendId), 0L);
                cereRecommendTrend.setCommentCount(cereRecommendTrend.getCommentCount() + Integer.parseInt(commentCount.toString()));
            }
            if (CollUtil.isNotEmpty(collectCounts)) {
                Long collectCount = collectCounts.getOrDefault(String.valueOf(recommendId), 0L);
                cereRecommendTrend.setCollectCount(cereRecommendTrend.getCollectCount() + Integer.parseInt(collectCount.toString()));
            }
            cereRecommendTrendsDAO.updateById(cereRecommendTrend);
            redisUtil.deleteLike(RedisEnum.USER_LIKE_KEY.getKey(), recommendId.toString());
            redisUtil.deleteLike(RedisEnum.USER_COMMENT_KEY.getKey(), recommendId.toString());
            redisUtil.deleteLike(RedisEnum.USER_COLLECT_KEY.getKey(), recommendId.toString());
        }





       /* try {
            //分批处理所有需要更新的动态id
            List<Long> recommendIds = cereRecommendTrendsDAO.selectRecommendIds();

            if (EmptyUtils.isEmpty(recommendIds)) {
                XxlJobHelper.log("没有需要同步的动态数据");
                log.info("没有需要同步的动态数据");
                return;
            }

            //分批处理动态数据
            int batchSize = calculateSize(recommendIds.size());
            int totalProcessed = 0;
            int failedCount = 0;

            for (int i = 0; i < recommendIds.size(); i += batchSize) {
                List<Long> batchIds = recommendIds.subList(i, Math.min(i + batchSize, recommendIds.size()));
                boolean success = sysBatchCounts(batchIds);
                if (success){
                    totalProcessed += batchIds.size();
                } else {
                    // XXL-JOB日志
                    failedCount++;
                    XxlJobHelper.log("处理批次 {}-{} 失败: {}", i, i + batchSize - 1);
                    log.error("处理批次 {}-{} 失败", i, i + batchSize - 1);
                }
                // XXL-JOB日志
                XxlJobHelper.log("已处理 {} / {} 条动态数据，失败批次：{}", totalProcessed, recommendIds.size(), failedCount);
                log.info("已处理 {} / {} 条动态数据，失败批次：{}", totalProcessed, recommendIds.size(), failedCount);
            }

            XxlJobHelper.log("同步动态点赞数、浏览数、评论数完成，总共处理 {} 条数据，耗时：{}ms",
                    totalProcessed, System.currentTimeMillis() - startTime);
            log.info("同步动态点赞数、浏览数、评论数完成，总共处理 {} 条数据，耗时：{}ms",
                    totalProcessed, System.currentTimeMillis() - startTime);

        } catch (Exception e) {
            XxlJobHelper.log("同步动态统计数据失败: " + e.getMessage());
            log.error("同步动态统计数据失败", e);
        }*/
    }

    /**
     * 批量处理动态数据
     *
     * @param recommendIds
     */
    private boolean sysBatchCounts(List<Long> recommendIds) {
        try {
            // 批量查询所有动态的最新统计值
            List<RecommendPageVO> recommendPageVOs = cereRecommendTrendsDAO.selectRecommendCountsBatch(recommendIds, RecommendLikeTypeEnum.RECOMMEND.getCode());

            // 批量更新动态表
            if (!recommendPageVOs.isEmpty()) {
                cereRecommendTrendsDAO.updateCountsBatch(recommendPageVOs, TimeUtils.yyMMddHHmmss());
            }
            return true;
        } catch (Exception e) {
            log.error("处理动态数据批次失败，批次大小：{}", recommendIds.size(), e);
            return false;
        }
    }

    /*
     * 动态计算批次大小方法
     */
    private int calculateSize(int totalSize) {
        if (totalSize <= 1000) {
            return 200;
        } else if (totalSize <= 10000) {
            return 500;
        } else {
            return 1000;
        }
    }
}
